<?php

namespace App\Http\Services;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

abstract class ServiceTrait
{
    const LIMIT = 15;

    public $name = '数据';
    const ERROR_MSG_CREATE = 'message.添加失败';
    const ERROR_MSG_FIND = 'message.数据未找到';
    const ERROR_MSG_UPDATE = 'message.跟新失败';
    const ERROR_MSG_DELETED = 'message.已删除';
    const ERROR_MSG_DELETE = 'message.删除失败';

    protected abstract function getModel(): Model;

    /**
     * 备注 OR 放第一个  形成SQL （条件） AND ...
     * @param array $search [
     * '查询字段', ['查询字段', '条件'], ['查询字段', '条件', '替换'],
     * '数据字段' => '查询字段', '数据字段' => ['查询字段', '条件'], '数据字段' => ['查询字段', '条件', '替换'],
     * 'OR' => '查询字段', 'OR' => ['查询字段', '查询字段' ....], 'OR' => ['数据字段' => '查询字段', 数据字段' => ['查询字段', '条件'], '数据字段' => ['查询字段', '条件', '替换'] ....]],
     * 替换规则 => --数据字段--
     * 示例： [
     * 'code',
     * ['mobile', '='] ,
     * ['mobile', 'LIKE', '%--mobile--'] ,
     * 'OR' => [
     * 'code',
     * 'scene'
     * ],
     * 'aa' => ['mobile', 'LIKE', '%--mobile--']
     * ]
     * @param $with
     * @param array $columns
     * @param array $sort
     * @return array
     */
    public function search(array $search = [], $with = null, array $columns = ['*'], array $defaultSort = ['id' => 'DESC'], $allowSort = [])
    {
        $input = request()->all();
        $limit = isset($input['limit']) ? intval($input['limit']) : self::LIMIT;
        $limit = $limit >= 100 ? 100 : ($limit < 1 ? self::LIMIT : $limit);
        $paginate = $this->parseQuery($search, $input, $with, $defaultSort, $allowSort)
            ->paginate($limit, $columns);

        $data['current_page'] = $paginate->currentPage();
        $data['last_page'] = $paginate->lastPage();
        $data['total'] = $paginate->total();
        $data['limit'] = $limit;
        $data['list'] = $paginate->items();
        $this->afterSearchList($data['list']);

        return $data;
    }

    protected function afterSearchList(&$data)
    {
    }

    protected function beforeUpdate($dept, &$data, $user)
    {

    }

    public function update(int $id, array $data, $user = null, $field = 'client_id', $isCheck = false)
    {
        try {
            if (!$dept = $this->getByPrimaryKey($id)) {
                return $this->setError(__(self::ERROR_MSG_FIND));
            }

            if ($isCheck && $user && $dept->{$field} != $user->id) {
                return $this->setError(__(self::ERROR_MSG_FIND));
            }

            $data = $this->filterate($data);
            DB::beginTransaction();
            $this->beforeUpdate($dept, $data, $user);
            $data = array_filter($data, function ($val) {
                return !is_null($val);
            });

            $dept->fill($data);
            if (!$dept->save()) {
                DB::rollBack();
                return $this->setError(__(static::ERROR_MSG_UPDATE));
            }

            $this->afterUpdate($dept, $data, $user);
            DB::commit();
        } catch (\Illuminate\Database\QueryException  $exception) {
            DB::rollBack();
            Log::error($exception->getMessage());
            return $this->setError(__(static::ERROR_MSG_UPDATE));
        } catch (HttpResponseException $exception) {
            throw $exception;
        } catch (\Exception $exception) {
            DB::rollBack();
            return $this->setError($exception->getMessage());
        }

        return $dept;
    }

    protected function afterUpdate($dept, $data, $user)
    {

    }

    protected function beforeStore(&$data, $user)
    {
    }

    protected function filterate($data)
    {
        foreach ($data as $key => $value) {
            if($value === null){
                unset($data[$key]);
            }
        }

        return $data;
    }

    public function store(array $data, $user = null)
    {
        try {
            DB::beginTransaction();
            $this->beforeStore($data, $user);
            $model = $this->getModel();
            $data = $this->filterate($data);
            $model->fill($data);
            if (!$model->save()) {
                DB::rollBack();
                return $this->setError(__(static::ERROR_MSG_CREATE));
            }

            $this->afterStore($model, $data, $user);
            DB::commit();
        } catch (\Illuminate\Database\QueryException  $exception) {
            DB::rollBack();
            Log::error($exception->getMessage());
            return $this->setError(__(static::ERROR_MSG_CREATE));
        } catch (HttpResponseException $exception) {
            throw $exception;
        } catch (\Exception $exception) {
            DB::rollBack();
            return $this->setError($exception->getMessage());
        }

        return $model;
    }

    protected function afterStore($model, $data, $user)
    {
    }

    public function getDetail(int $id, $hidden = [], $with = null)
    {
        if (!$dept = $this->getByPrimaryKey($id, $with)) {
            return $this->setError(__(static::ERROR_MSG_FIND));
        }

        $dept->makeHidden($hidden);

        return $dept;
    }

    protected function beforeDelete($dept, $user)
    {

    }

    public function delete(int $id, $user = null, $field = '')
    {
        if (!$dept = $this->getByPrimaryKey($id)) {
            return $this->setError(__(self::ERROR_MSG_DELETED));
        }

        if ($field && $user && $dept->{$field} != $user->id) {
            return $this->setError(__(self::ERROR_MSG_DELETED));
        }

        try {
            DB::beginTransaction();
            $this->beforeDelete($dept, $user);
            if (!$dept->delete()) {
                DB::rollBack();
                return $this->setError(__(self::ERROR_MSG_DELETE));
            }

            DB::commit();
            $this->afterDelete($dept);
        } catch (\Illuminate\Database\QueryException  $exception) {
            DB::rollBack();
            return $this->setError(__(self::ERROR_MSG_DELETE));
        } catch (HttpResponseException $exception) {
            throw new $exception;
        } catch (\Exception $exception) {
            DB::rollBack();
            return $this->setError($exception->getMessage());
        }

        return true;
    }

    protected function afterDelete($dept)
    {

    }

    public function batchDeleteByPrimaryKey(array $ids)
    {
        return $this->getModel()
            ->where($this->getModel()->getKeyName(), 'IN', $ids)
            ->delete();
    }

    /**
     * @param int $id
     * @return Model | null
     */
    protected function getByPrimaryKey(int $id, $with = null)
    {
        $key = $this->getModel()->getKeyName();
        return $this->getBy([$key => $id], $with);
    }

    public function getBy(array $where, $with = null)
    {
        $query = $this->getModel()->where($where);
        !$with or $query = $query->with($with);

        return $query->first();
    }

    protected function setError($msg, $code = '-1', $data = null)
    {
        $this->_errors[] = [
            'code' => $code,
            'msg' => $msg,
            'data' => $data,
        ];

        return false;
    }

    public function getErrorMsg()
    {
        return $this->_errors[0]['msg'] ?? 'fail';
    }

    public function getErrorCode()
    {
        return $this->_errors[0]['code'] ?? '-1';
    }

    public function getErrors()
    {
        $errors = current($this->_errors);
        $errors = empty($errors) ? [
            'code' => '-1',
            'msg' => 'fail',
            'data' => null,
        ] : $errors;

        return $errors;
    }

    private function parseQuery(array $search, $input, $with, array $defaultSort, array $allowSort): Builder|Model
    {
        $query = $this->getModel();
        foreach ($search as $key => $value) {
            if ($value instanceof \Closure) {
                if (is_string($key)) {
                    $query = $query->where($key, $value);
                } else {
                    $query = $value($query, $search, $input);
                }

                if (!($query instanceof Builder || $query instanceof Model)) {
                    throw new \Exception('查询回调返回未继承【' . Builder::class . '】或者【' . Model::class . '】');
                }

                continue;
            }

            if (is_int($key)) {
                $query = $this->parseQueryAnd($query, $value, $input);
                continue;
            }

            if ($key == 'OR') {
                $query = $this->parseQueryOr($query, $value, $input);
            } elseif (isset($input[$key]) && $input[$key] != "") {
                $field = is_array($value) ? $value[0] : $value;
                $query = $this->parseQueryAnd($query, $value, [$field => $input[$key]]);
            } elseif (!isset($input[$key]) && (is_array($value) && count($value) == 3)) {
                $query = $query->where($value[0], $value[1], $value[2]);
            }
        }

        $query = $this->parseOrder($query, $input, $defaultSort, $allowSort);
        return $with ? $query->with($with) : $query;
    }

    private function parseOrder(Builder|Model $query, $input, array $defaultSort = [], array $allowSort = [])
    {
        if (isset($input['sort'])) {
            $sorts = explode(',', trim($input['sort']));
            foreach ($sorts as $sort) {
                $sort = explode('=', trim($sort));
                if (!isset($allowSort[$sort[0]])) {
                    continue;
                }

                $defaultSort[$sort[0]] = isset($sort[1]) ? ($sort[1] == 'ASC' ? 'ASC' : 'DESC') : 'DESC';
            }
        }

        foreach ($defaultSort as $field => $sort) {
            $query = $query->orderBy($field, $sort);
        }

        return $query;
    }

    private
    function parseQueryAnd(Builder|Model $query, $value, $input, $boolean = 'and'): Builder|Model
    {
        if (is_string($value) && isset($input[$value]) && $this->isValueEmpty($input[$value]) === false) {
            $query = $query->where($value, '=', $input[$value], $boolean);
        } elseif (is_array($value) && $this->isEmpty($value, $input)) {
            $query = $query->where($value[0], $value[1], $value[2], $boolean);
        }

        return $query;
    }

    private
    function isValueEmpty($value)
    {
        return $value === '' || $value === null;
    }

    private
    function isEmpty(&$value, $input)
    {
        if (isset($value[2]) && $value[2] instanceof \Closure) {
            $value[2] = $value[2]($input);
            return true;
        }

        if (count($value) === 3 && strrpos($value[2], "--{$value[0]}--") === false) {
            return true;
        }

        if (!isset($input[$value[0]]) || $this->isValueEmpty($input[$value[0]])) {
            return false;
        }

        $value[2] = isset($value[2]) ? str_replace('--' . $value[0] . '--', $input[$value[0]], $value[2]) : $input[$value[0]];

        return true;
    }

    private
    function parseQueryOr(Builder|Model $query, $value, $input): Builder|Model
    {
        if (is_string($value) && isset($input[$value]) && $input[$value] !== '') {
            $query = $query->orWhere($value, '=', $input[$value]);
        } else {
            if (is_array($value[0])) {
                $query = $query->where(function ($query) use ($value, $input) {
                    $data = $input;
                    foreach ($value as $key => $item) {
                        if (is_string($key)) {
                            $data = isset($input[$key]) ? $input[$key] : $input;
                        }

                        $this->parseQueryAnd($query, $item, $data, 'OR');
                    }
                });
            } else if ($this->isEmpty($value, $input)) {
                $query = $query->where($value[0], $value[1], $value[2]);
            }

        }

        return $query;
    }

    private $_errors = [];
}
